package com.apimarket.config;
import com.apimarket.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

/**
 * @Description:
 * @Author ZXR
 * @Date 2021/7/9 14:20
 * @Version 1.0
 */

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //会话失效（账号被挤下线）处理逻辑
    @Autowired
    CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
    //登录成功的处理逻辑
    @Autowired
    private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;
    //登录失败的处理逻辑
    @Autowired
    private CustomizeAuthenticationFailureHandler authenticationFailureHandler;
    //登出成功处理逻辑
    @Autowired
    private CustomizeLogoutSuccessHandler logoutSuccessHandler;
    //访问决策管理器
    @Autowired
    CustomizeAccessDecisionManager accessDecisionManager;
    //实现权限拦截,安全元数据源
    @Autowired
    CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
    //拦截器
    @Autowired
    private CustomizeAbstractSecurityInterceptor securityInterceptor;
    //匿名用户访问无权限资源时的异常
    @Autowired
    CustomizeAuthenticationEntryPoint authenticationEntryPoint;
    //权限拒绝处理逻辑
    @Autowired
    CustomizeAccessDeniedHandler accessDeniedHandler;
    @Bean
    public UserDetailsService userDetailsService(){
        //获取用户账号密码及权限信息
        return new UserDetailsServiceImpl();
    }
    //配置认证方式等
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置认证方式
        auth.userDetailsService(userDetailsService());
    }

    //http相关的配置，包括登入登出、异常处理、会话管理等
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //如果不加这个，login登录会报错403
        //原因可能是开启了CSRF保护，关闭即可
        http.cors().and().csrf().disable();
        http.authorizeRequests()
                //.antMatchers("/type/getTypeList").hasAuthority("apiTypeList")
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                        o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                        return o;
                    }
                })
        //异常处理（权限拒绝、登录失败）
        .and().exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler) //权限拒绝处理逻辑
        .authenticationEntryPoint(authenticationEntryPoint) //匿名用户访问无权限资源时的异常处理
        //登入
        .and().formLogin()
        .permitAll() //允许所有用户,登入登出的controller接口,SpringSecurity已经为我们封装好了，不用写
        .successHandler(authenticationSuccessHandler) //登录成功处理逻辑
        .failureHandler(authenticationFailureHandler) //登录失败处理逻辑
        //登出
        .and().logout() //在这个配置中加上logout(),然后再推出按钮中使用/logout作为请求登出路径即可
        .permitAll() //允许所有用户
        .logoutSuccessHandler(logoutSuccessHandler) //登出成功处理逻辑
        .deleteCookies("JSESSIONID") //登出之后删除cookie
        //会话管理
        .and().sessionManagement().maximumSessions(1) //同一账号同时登录最大用户数
        .expiredSessionStrategy(sessionInformationExpiredStrategy) //会话信息过期策略（账号被挤下线）
        ;
        //增加到默认拦截链中
        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
    }

    //springSecurity规定必须设置一个默认的加密方式
    //在WebSecurityConfig中注入即可，不用声明使用
    //这种加密方式即使是同一个明文，但用这种加密方式加密出来的密文也是不同的
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        //设置默认的加密方式（强hash方式加密）
        return new BCryptPasswordEncoder();
    }
}
